Skip to content

feat(rbac): enforce agent_use and llm_use, retire object_publish and dashboard_view (decisions 239, 247 to 249) - #4641

Open
rubenvdlinde wants to merge 5 commits into
developmentfrom
feat/enforce-special-rights
Open

rubenvdlinde wants to merge 5 commits into
developmentfrom
feat/enforce-special-rights

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Decisions 239 and 247 to 249: the special rights agent_use and llm_use are enforced where they apply, object_publish and dashboard_view are retired, and only an administrator or the organisation's owner can change who holds a special right.

OpenSpec change: openspec/changes/enforce-organisation-special-rights/ (proposal, design with the file and line of every enforcement point, spec delta, tasks).

What is enforced, and where

Right Enforced at Read from
agent_use ChatService::processMessage() (lib/Service/ChatService.php:253), reached by POST /api/chat/send and POST /api/chat/stream; ChatStreamController::stream() (:302) before the first heartbeat; McpAgentScope::loadAgent() (lib/Service/Mcp/McpAgentScope.php:248) for MCP initialize, tools/list and tools/call the agent's organisation
llm_use the same check in ChatService::processMessage() and ChatStreamController::stream(). Every language model call in the app (answer, title, summary) is reached only from processMessage() the conversation's organisation

Semantic and hybrid search embed the query with an embedding model and do not need llm_use (decision 249); they are unchanged.

The one reader is still OrganisationSpecialRights from #4601. It gains mayUse() (administrators pass, * means every signed-in user, a key the organisation never set does not narrow, a named organisation that cannot be loaded refuses, no organisation and no default organisation does not narrow) and openToEveryoneInEveryOrganisation().

A refused chat returns 403 from /api/chat/send, a forbidden frame naming the right from /api/chat/stream (its headers are already sent, as for a conversation that is not yours), and a JSON-RPC error from MCP. Nothing is stored and no model is called.

Retired: object_publish and dashboard_view (decisions 247, 248)

Neither was ever enforced: objects have had no publish action since migration Version1Date20260313130000, and /api/dashboard/* is administrator only. Both are removed from the organisation's defaults, the editor's Special Rights tab, the frontend entity, the admin-group backfill, the upgrade step and the docs. An organisation that still stores them loads and is served unchanged; nothing reads them and nothing writes them.

Who changes special rights

PUT/PATCH /api/organisations/{uuid} refuses a change to agent_use, llm_use or grant_manage from anybody but an administrator or the owner (403 with rights naming them). #4601 did this for grant_manage only. A member still edits the rest of the organisation. The Special Rights tab is read-only for them and says why, and offers "Everyone" (*).

Upgrade notes: what becomes refused for whom

Nothing, on the day of the upgrade. Before, every signed-in user could chat and run agents whatever the lists said, and the lists said [] or ["admin"] on most organisations. The post-migration repair step OpenSpecialRightsToEveryone adds * beside the stored groups under agent_use and llm_use in every organisation, once (app config special_rights_opened_to_everyone), and never touches grant_manage or the retired keys. A new organisation starts with ["*"] under both.

After that, taking * out of a list refuses users outside its groups, and a later upgrade does not put it back. object_publish and dashboard_view were never enforced, so retiring them refuses nobody; they disappear from the editor, and values already stored are ignored.

Verified

Run once in the foreground on this branch:

  • phpcs 0, phpmd (with HOME set to an empty dir, both rule sets, whole lib/) 0 and 0, psalm --threads=1 0 (no errors), phpstan 0
  • phpunit --no-coverage: the 328 tests around this change pass (Rbac, chat, MCP, organisation controller and model, repair step, OrganisationService). The full Unit suite on the previous commit: 26,183 tests, 15 errors, all OC\DB\Schema\Table is not loadable (13 AuditTrail and 2 DraftRemovalAuditRow tests need an installed Nextcloud)
  • jest on src/entities/organisation 4/4, npm run lint 0 (warnings only; the editor's ellipsis suppression count went down from 8 to 6 with the two removed rows), prettier 0, test:l10n OK

Not verified live: no instance was used. Both endpoints are plain Controllers, not OCSControllers, so OCSMiddleware leaves the 403 as it is; the tests assert that, and drive the real controller over the real ChatService and the real reader.

Inherited: the 15 PHPUnit errors above; 942 eslint warnings, none in the changed lines.

…wner change special rights (decision 239)

agent_use is checked when a chat message runs an agent and when an MCP
session acts as one, read from the agent's organisation. llm_use is checked
on every chat message before it is stored or a model is called, read from
the conversation's organisation. Administrators pass; * means every signed-in
user. A post-migration repair step writes * beside the stored groups once,
so nobody is refused on upgrade. A change to any special right from a member
who is not the owner or an administrator is refused with 403, and the
editor's Special Rights tab is read-only for them.

object_publish and dashboard_view name no action in the app today and stay
unenforced; the questions are in the change's design.md. The docs no longer
show the hasSpecialRight() call that does not exist.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 9196aac

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-specs ✅
test-l10n ✅
test-l10n-parity ❌
format ✅
check-schema-l10n ✅
composer ❌ ✅ 173/173
npm ✅ ✅ 648/648
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ❌ 26248/26250 · 25 skipped
Newman ✅ 714/714
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-15 10:43 UTC

Download the full PDF report from the workflow artifacts.

…rking without llm_use (decisions 247 to 249)

Both rights were stored and shown but never enforced. They leave the
organisation's defaults, the editor's Special Rights tab, the frontend entity,
the admin-group backfill, the upgrade step and the docs. A stored block that
still holds them loads and is served unchanged; nothing reads or writes them.
The spec records that semantic search does not need llm_use.
@rubenvdlinde rubenvdlinde changed the title feat(rbac): enforce agent_use and llm_use, and only an admin or owner changes special rights (decision 239) feat(rbac): enforce agent_use and llm_use, retire object_publish and dashboard_view (decisions 239, 247 to 249) Oct 15, 2026
…and read info.xml without libxml's file loader in the repair test

The parity gate wants every required locale to carry "Everyone" and the
read-only note. Under Nextcloud the libxml external entity loader refuses file
paths, so simplexml_load_file() returned false in CI; the test now reads the
file with file_get_contents(), as the other info.xml tests do.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ edeb890

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-specs ✅
test-l10n ✅
test-l10n-parity ✅
format ✅
check-schema-l10n ✅
composer ✅ ✅ 173/173
npm ✅ ✅ 648/648
app:check-code ⏭️
info.xml ✅
REUSE ✅
lockfile sync ✅
PHPUnit ✅ 26248/26248 · 25 skipped
Newman ✅ 714/714
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-10-15 19:20 UTC

Download the full PDF report from the workflow artifacts.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant